﻿using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Brain.Skinning;
using System;

namespace Brain.Rendering
{
    public class DefaultRenderManager3D : IRenderManager3D
    {
        public void DeserializedAll()
        {
        }
        public GraphicsDevice GraphicsDevice { get; set; }

        public bool ShouldDraw { get; set; }

        public bool ShouldUpdate { get; set; }

        protected List<IWorldObject> Objects;

        internal BasicEffect BasicEffect;

        public List<Action> BeforeRenderActions { get; set; }
        public List<Action> AfterRenderActions { get; set; }

        public DynamicVertexBuffer VerticesBuffer;

        public ShapeDrawer ShapeDrawer;

        public ICuller Culler;

        public DefaultRenderManager3D(GraphicsDevice device)
        {
            Culler = new FrustumCullingManager();

            ShapeDrawer = new ShapeDrawer();

            BeforeRenderActions = new List<Action>();
            AfterRenderActions = new List<Action>();
            
            this.GraphicsDevice = device;
            this.ShouldDraw = true;

            this.BasicEffect = new BasicEffect(device);
            BasicEffect.VertexColorEnabled = true;

            Objects = new List<IWorldObject>();
        }


       
        public void Update()
        {
        }

        public void StartDraw(GameScreen gs)
        {
        }

        public void FinishDraw(GameScreen gs)
        {

        }

        // O is your object's position
        // P is the position of the object to face
        // U is the nominal "up" vector (typically Vector3.Up)
        // Note: this does not work when O is straight below or straight above P
        Matrix RotateToFace(Vector3 O, Vector3 P, Vector3 U)
        {
            Vector3 D = (O - P);
            Vector3 Right = Vector3.Cross(U, D);
            Right.Normalize();
            Vector3 Backwards = Vector3.Cross(Right, U);
            Vector3.Normalize(ref Backwards, out Backwards);
            Vector3 Up = Vector3.Cross(Backwards, Right);
            Matrix rot = new Matrix(Right.X, Right.Y, Right.Z, 0, Up.X, Up.Y, Up.Z, 0, Backwards.X, Backwards.Y, Backwards.Z, 0, 0, 0, 0, 1);

            return rot;
        }
        public void DrawEverything(ICamera3D Camera)
        {
            if (Objects == null)
                return;

            GraphicsDevice device = this.GraphicsDevice;
            device.BlendState = BlendState.Opaque;
            device.DepthStencilState = DepthStencilState.Default;

            ShapeDrawer.DrawEverything(Camera);

            for (int i = 0; i < Objects.Count; i++)
            {
                IWorldObject obj = Objects[i];

                if (obj.BoundingBoxColor != null)
                {
                    ShapeDrawer.Draw(obj.BoundingBox, obj.BoundingBoxColor.Value, Camera);
                }

#if XBOX
                if (obj is XboxAvatar)
                {
                    XboxAvatar avatar = obj as XboxAvatar;
                    if (avatar.Loaded && avatar.Renderer != null)
                    {
                        avatar.Renderer.World = avatar.WorldMatrix;
                        avatar.Renderer.Projection = Projection;
                        avatar.Renderer.View = View;
                        if (avatar is XboxAvatar_CustomAnimation)
                        {
                            avatar.Renderer.Draw((avatar as XboxAvatar_CustomAnimation).AnimationPlayer);
                        }
                        else if (avatar is XboxAvatar_Default)
                        {
                            avatar.Renderer.Draw((avatar as XboxAvatar_Default).Animation);
                        }
                    }
                }
                else
#endif
                    if (obj.Model is SkinnedModel)
                    {
                        SkinnedModel model = obj.Model as SkinnedModel;

                        foreach (ModelMesh mesh in model.Model.Meshes)
                        {
                            if (Culler.ShouldDraw(Camera, mesh, obj))
                            {
                                foreach (SkinnedEffect effect in mesh.Effects)
                                {
                                    effect.World = obj.WorldMatrix;

                                    effect.SetBoneTransforms(model.SkinTransforms);

                                    effect.View = Camera.View;
                                    effect.Projection = Camera.Projection;
                                    effect.EnableDefaultLighting();
                                    effect.PreferPerPixelLighting = true;

                                    effect.SpecularColor = new Vector3(0.25f);
                                    effect.SpecularPower = 16;
                                }
                                mesh.Draw();
                            }
                        }
                    }
                    else if (obj.Model is BrainModel)
                    {
                        BrainModel model = obj.Model as BrainModel;
                        DrawBrainModel(obj, model, Camera);
                    }
                    else if (obj.RenderingTag is BrainModel)
                    {
                        BrainModel model = obj.RenderingTag as BrainModel;
                        DrawBrainModel(obj, model, Camera);
                    }
            }
        }

        public void EndDraw()
        {
            for (int i = 0; i < BeforeRenderActions.Count; i++)
            {
                BeforeRenderActions[i].Invoke();
            }
            if (Engine.Instance.CameraManager != null)
            {
                ICamera3D Camera = Engine.Instance.CameraManager.Camera3D;
                if (Camera != null)
                {
                    if (Engine.Instance.StereoscopicManager == null)
                    {
                        DrawEverything(Camera);
                    }
                    else
                    {
                        if (Engine.Instance.StereoscopicManager.StereoMode == StereoMode.NoStereo)
                        {
                            DrawEverything(Camera);
                        }
                        else
                        {
                            Engine.Instance.StereoscopicManager.Draw(Camera, this);
                        }
                    }
                }
            }

            for (int i = 0; i < AfterRenderActions.Count; i++)
            {
                AfterRenderActions[i]();
            }
        }

        protected void DrawBrainModel(IWorldObject obj, BrainModel model, ICamera3D Camera)
        {
            GraphicsDevice device = Engine.Instance.GraphicsDevice;

            foreach (BrainModelMesh mesh in model.Meshes)
            {
                if (mesh == null)
                    continue;

                if (mesh.Effect == null)
                    continue;

                if (Culler.ShouldDraw(Camera, mesh, obj))
                {
                    Effect effect = mesh.Effect;
                    if (effect is BasicEffect)
                    {
                        BasicEffect fx = effect as BasicEffect;
                        fx.EnableDefaultLighting();
                        fx.PreferPerPixelLighting = true;

                        fx.Projection = Camera.Projection;
                        fx.View = Camera.View;

                        Matrix world = obj.WorldMatrix;
                        if (obj.Model is Model)
                        {
                            Model mod = obj.Model as Model;
                            // Copy any parent transforms.
                            Matrix[] transforms = new Matrix[mod.Bones.Count];
                            mod.CopyAbsoluteBoneTransformsTo(transforms);

                            fx.World = transforms[mesh.BoneParentIndex] * world;
                        }
                        else
                        {
                            fx.World = world;
                        }
                    }
                    else
                    {
                        //continue;
                    }

                    device.Indices = mesh.IndexBuffer;
                    device.SetVertexBuffer(mesh.VertexBuffer);

                    int count = effect.CurrentTechnique.Passes.Count;

                    for (int p = 0; p < count; p++)
                    {
                        effect.CurrentTechnique.Passes[p].Apply();

                        if (mesh.IndexBuffer == null)
                        {
                            device.DrawPrimitives(PrimitiveType.TriangleList, 0, mesh.PrimitiveCount);
                        }
                        else
                        {
                            device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0,
                                mesh.NumVertices, mesh.VertexOffset, mesh.PrimitiveCount);
                        }
                    }
                }
            }
        }


        public void Draw()
        {
        }

        public void SubmitWorldObject(IWorldObject obj)
        {
            if (!this.Objects.Contains(obj))
            {
                Objects.Add(obj);

                if (obj.Model is Model)
                {
                    BrainModel model = new BrainModel();
                    Model m = obj.Model as Model;

                    int count = 0;
                    foreach (ModelMesh mesh in m.Meshes)
                    {
                        count += mesh.MeshParts.Count;
                    }

                    model.Meshes = new BrainModelMesh[count];

                    foreach (ModelMesh mesh in m.Meshes)
                    {
                        for (int i = 0; i < mesh.MeshParts.Count; i++)
                        {
                            ModelMeshPart part = mesh.MeshParts[i];
                                                        
                            BrainModelMesh bmesh = new BrainModelMesh();
                            bmesh.BoneParentIndex = mesh.ParentBone.Index;
                            bmesh.Effect = part.Effect;
                            bmesh.IndexBuffer = part.IndexBuffer;
                            bmesh.NumVertices = part.NumVertices;
                            bmesh.PrimitiveCount = part.PrimitiveCount;
                            bmesh.StartIndex = part.StartIndex;
                            bmesh.VertexBuffer = part.VertexBuffer;
                            bmesh.VertexOffset = part.VertexOffset;
                            bmesh.BoundingSphere = mesh.BoundingSphere;

                            model.Meshes[i] = bmesh;
                        }
                    }

                    obj.RenderingTag = model;
                }
            }
        }
        public void RemoveWorldObject(IWorldObject obj)
        {
            if (Objects != null)
            {
                Objects.Remove(obj);
            }
        }

        #region not used
        public void SubmitLight(object light)
        {
        }

        public void RemoveLight(object light)
        {
        }
        #endregion

        public void Reset()
        {
            this.Objects.Clear();
            this.Objects.Capacity = 0;

            this.BeforeRenderActions.Clear();
            this.BeforeRenderActions.Capacity = 0;
            this.AfterRenderActions.Clear();
            this.AfterRenderActions.Capacity = 0;
        }
        public void UpdateWorldObjectStatus(IWorldObject worldObject)
        {
        }

        public SerializationData Data { get; set; }

        public void SerializeData(DefaultSerializationManager manager)
        {
        }

        public void DeserializeData(DefaultSerializationManager manager, SerializationData newData)
        {
        }

        public IWorldObject Editor_GetWorldObject(int index)
        {
            return this.Objects[index];

        }

        public int Editor_Count()
        {
            return this.Objects.Count;
        }

        public void Submit(object obj)
        {
            ShapeDrawer.Submit(obj);
        }

        public void Remove(object obj)
        {
        }

        public void UpdateTerrainVertexBuffer(WorldObject terrain, VertexBuffer buffer)
        {
            VertexPositionNormalTexture[] vertices = terrain.RenderingTag as VertexPositionNormalTexture[];
            buffer.SetData(vertices);
        }

        public void InitializeTerrainDefaultRendering(TerrainWorldObject terrain, bool wipeCPUContent, bool makeTriangles)
        {
            HeightMap HeightMap = terrain.HeightMap;

            if (HeightMap != null)
            {
                int VertexCount = HeightMap.Width * HeightMap.Height;

                VertexPositionNormalTexture[] vertices = new VertexPositionNormalTexture[VertexCount];
                terrain.RenderingTag = vertices;

                float offset = HeightMap.OffsetBetweenHeights;
                for (int x = 0; x < HeightMap.Width; x++)
                {
                    for (int z = 0; z < HeightMap.Height; z++)
                    {
                        float height = HeightMap.Heights[x, z];
                        int index = x + z * HeightMap.Width;
                        vertices[index].Position = new Vector3(x * offset, height, -z * offset);
                        vertices[index].TextureCoordinate = new Vector2((float)x / 30.0f, (float)z / 30.0f);
                    }
                }

                short[] Indices = new short[(HeightMap.Width - 1) * (HeightMap.Height - 1) * 6];
                int counter = 0;
                for (int y = 0; y < HeightMap.Height - 1; y++)
                {
                    for (int x = 0; x < HeightMap.Width - 1; x++)
                    {
                        int lowerLeft = x + y * HeightMap.Width;
                        int lowerRight = (x + 1) + y * HeightMap.Width;
                        int topLeft = x + (y + 1) * HeightMap.Width;
                        int topRight = (x + 1) + (y + 1) * HeightMap.Width;

                        Indices[counter++] = (short)topLeft;
                        Indices[counter++] = (short)lowerRight;
                        Indices[counter++] = (short)lowerLeft;

                        Indices[counter++] = (short)topLeft;
                        Indices[counter++] = (short)topRight;
                        Indices[counter++] = (short)lowerRight;
                    }
                }

                TriangleUtil.Normal(vertices, Indices);

                VertexBuffer VertexBuffer;
                if (Engine.Instance.RunningEditor)
                {
                    VertexBuffer = new DynamicVertexBuffer(Engine.Instance.GraphicsDevice, VertexPositionNormalTexture.VertexDeclaration,
                         vertices.Length, BufferUsage.None);
                }
                else
                {
                    VertexBuffer = new VertexBuffer(Engine.Instance.GraphicsDevice, VertexPositionNormalTexture.VertexDeclaration,
                         vertices.Length, BufferUsage.None);
                }
                VertexBuffer.SetData(vertices);

                IndexBuffer IndexBuffer;
                if (Engine.Instance.RunningEditor)
                {
                    IndexBuffer = new DynamicIndexBuffer(Engine.Instance.GraphicsDevice,
                        IndexElementSize.SixteenBits, Indices.Length, BufferUsage.None);
                }
                else
                {
                    IndexBuffer = new IndexBuffer(Engine.Instance.GraphicsDevice,
                        IndexElementSize.SixteenBits, Indices.Length, BufferUsage.None);
                }
                IndexBuffer.SetData(Indices);


                BrainModel model = new BrainModel();

                BrainModelMesh mesh = new BrainModelMesh();
                mesh.VertexBuffer = VertexBuffer;
                mesh.IndexBuffer = IndexBuffer;
                mesh.Effect = new BasicEffect(Engine.Instance.GraphicsDevice);
                mesh.NumVertices = VertexBuffer.VertexCount;
                mesh.PrimitiveCount = Indices.Length / 3;
                mesh.StartIndex = 0;
                mesh.VertexOffset = 0;

                model.Meshes = new BrainModelMesh[1];
                model.Meshes[0] = mesh;

                terrain.Model = model;

                if (makeTriangles)
                {
                    terrain.Indices = Indices;
                    terrain.MakeTriangles();
                    terrain.Indices = null;
                }

                if (wipeCPUContent)
                {
                    Indices = null;
                }
                else
                {
                    terrain.Indices = Indices;
                }
            }
        }

        public Vector3 GetTerrainVertex(WorldObject terrain, int index)
        {
            VertexPositionNormalTexture[] vertices = terrain.RenderingTag as VertexPositionNormalTexture[];
            return vertices[index].Position;
        }

        public void Editor_UpdateTerrainVertex(WorldObject terrain, int index, Vector3 newPos, Vector2 newTexCoord)
        {
            VertexPositionNormalTexture[] vertices = terrain.RenderingTag as VertexPositionNormalTexture[];
            vertices[index].Position = newPos;
            vertices[index].TextureCoordinate = newTexCoord;
        }



    }
}
